﻿using System;
using GeoAPI.Geometries;

namespace NetTopologySuite.Geometries
{
	public class OctagonalEnvelope
	{
		public static IGeometry GetOctagonalEnvelope(IGeometry geom)
		{
			return new OctagonalEnvelope(geom).ToGeometry(geom.Factory);
		}

		private static double ComputeA(double x, double y)
		{
			return x + y;
		}

		private static double ComputeB(double x, double y)
		{
			return x - y;
		}

		public OctagonalEnvelope()
		{
		}

		public OctagonalEnvelope(Coordinate p)
		{
			this.ExpandToInclude(p);
		}

		public OctagonalEnvelope(Coordinate p0, Coordinate p1)
		{
			this.ExpandToInclude(p0);
			this.ExpandToInclude(p1);
		}

		public OctagonalEnvelope(Envelope env)
		{
			this.ExpandToInclude(env);
		}

		public OctagonalEnvelope(OctagonalEnvelope oct)
		{
			this.ExpandToInclude(oct);
		}

		public OctagonalEnvelope(IGeometry geom)
		{
			this.ExpandToInclude(geom);
		}

		public double MinX
		{
			get
			{
				return this._minX;
			}
		}

		public double MaxX
		{
			get
			{
				return this._maxX;
			}
		}

		public double MinY
		{
			get
			{
				return this._minY;
			}
		}

		public double MaxY
		{
			get
			{
				return this._maxY;
			}
		}

		public double MinA
		{
			get
			{
				return this._minA;
			}
		}

		public double MaxA
		{
			get
			{
				return this._maxA;
			}
		}

		public double MinB
		{
			get
			{
				return this._minB;
			}
		}

		public double MaxB
		{
			get
			{
				return this._maxB;
			}
		}

		public bool IsNull
		{
			get
			{
				return double.IsNaN(this._minX);
			}
			private set
			{
				if (value)
				{
					this._minX = double.NaN;
				}
			}
		}

		public void ExpandToInclude(IGeometry g)
		{
			g.Apply(new OctagonalEnvelope.BoundingOctagonComponentFilter(this));
		}

		public OctagonalEnvelope ExpandToInclude(ICoordinateSequence seq)
		{
			for (int i = 0; i < seq.Count; i++)
			{
				double x = seq.GetX(i);
				double y = seq.GetY(i);
				this.ExpandToInclude(x, y);
			}
			return this;
		}

		public OctagonalEnvelope ExpandToInclude(OctagonalEnvelope oct)
		{
			if (oct.IsNull)
			{
				return this;
			}
			if (this.IsNull)
			{
				this._minX = oct._minX;
				this._maxX = oct._maxX;
				this._minY = oct._minY;
				this._maxY = oct._maxY;
				this._minA = oct._minA;
				this._maxA = oct._maxA;
				this._minB = oct._minB;
				this._maxB = oct._maxB;
				return this;
			}
			if (oct._minX < this._minX)
			{
				this._minX = oct._minX;
			}
			if (oct._maxX > this._maxX)
			{
				this._maxX = oct._maxX;
			}
			if (oct._minY < this._minY)
			{
				this._minY = oct._minY;
			}
			if (oct._maxY > this._maxY)
			{
				this._maxY = oct._maxY;
			}
			if (oct._minA < this._minA)
			{
				this._minA = oct._minA;
			}
			if (oct._maxA > this._maxA)
			{
				this._maxA = oct._maxA;
			}
			if (oct._minB < this._minB)
			{
				this._minB = oct._minB;
			}
			if (oct._maxB > this._maxB)
			{
				this._maxB = oct._maxB;
			}
			return this;
		}

		public OctagonalEnvelope ExpandToInclude(Coordinate p)
		{
			this.ExpandToInclude(p.X, p.Y);
			return this;
		}

		public OctagonalEnvelope ExpandToInclude(Envelope env)
		{
			this.ExpandToInclude(env.MinX, env.MinY);
			this.ExpandToInclude(env.MinX, env.MaxY);
			this.ExpandToInclude(env.MaxX, env.MinY);
			this.ExpandToInclude(env.MaxX, env.MaxY);
			return this;
		}

		public OctagonalEnvelope ExpandToInclude(double x, double y)
		{
			double num = OctagonalEnvelope.ComputeA(x, y);
			double num2 = OctagonalEnvelope.ComputeB(x, y);
			if (this.IsNull)
			{
				this._minX = x;
				this._maxX = x;
				this._minY = y;
				this._maxY = y;
				this._minA = num;
				this._maxA = num;
				this._minB = num2;
				this._maxB = num2;
			}
			else
			{
				if (x < this._minX)
				{
					this._minX = x;
				}
				if (x > this._maxX)
				{
					this._maxX = x;
				}
				if (y < this._minY)
				{
					this._minY = y;
				}
				if (y > this._maxY)
				{
					this._maxY = y;
				}
				if (num < this._minA)
				{
					this._minA = num;
				}
				if (num > this._maxA)
				{
					this._maxA = num;
				}
				if (num2 < this._minB)
				{
					this._minB = num2;
				}
				if (num2 > this._maxB)
				{
					this._maxB = num2;
				}
			}
			return this;
		}

		public void ExpandBy(double distance)
		{
			if (this.IsNull)
			{
				return;
			}
			double num = OctagonalEnvelope.SQRT2 * distance;
			this._minX -= distance;
			this._maxX += distance;
			this._minY -= distance;
			this._maxY += distance;
			this._minA -= num;
			this._maxA += num;
			this._minB -= num;
			this._maxB += num;
			if (!this.IsValid)
			{
				this.IsNull = true;
			}
		}

		private bool IsValid
		{
			get
			{
				return this.IsNull || (this._minX <= this._maxX && this._minY <= this._maxY && this._minA <= this._maxA && this._minB <= this._maxB);
			}
		}

		public bool Intersects(OctagonalEnvelope other)
		{
			return !this.IsNull && !other.IsNull && this._minX <= other._maxX && this._maxX >= other._minX && this._minY <= other._maxY && this._maxY >= other._minY && this._minA <= other._maxA && this._maxA >= other._minA && this._minB <= other._maxB && this._maxB >= other._minB;
		}

		public bool Intersects(Coordinate p)
		{
			if (this._minX > p.X)
			{
				return false;
			}
			if (this._maxX < p.X)
			{
				return false;
			}
			if (this._minY > p.Y)
			{
				return false;
			}
			if (this._maxY < p.Y)
			{
				return false;
			}
			double num = OctagonalEnvelope.ComputeA(p.X, p.Y);
			double num2 = OctagonalEnvelope.ComputeB(p.X, p.Y);
			return this._minA <= num && this._maxA >= num && this._minB <= num2 && this._maxB >= num2;
		}

		public bool Contains(OctagonalEnvelope other)
		{
			return !this.IsNull && !other.IsNull && (other._minX >= this._minX && other._maxX <= this._maxX && other._minY >= this._minY && other._maxY <= this._maxY && other._minA >= this._minA && other._maxA <= this._maxA && other._minB >= this._minB) && other._maxB <= this._maxB;
		}

		public IGeometry ToGeometry(IGeometryFactory geomFactory)
		{
			if (this.IsNull)
			{
                return geomFactory.CreatePoint((ICoordinateSequence)null);
			}
			Coordinate coordinate = new Coordinate(this._minX, this._minA - this._minX);
			Coordinate coord = new Coordinate(this._minX, this._minX - this._minB);
			Coordinate coord2 = new Coordinate(this._maxX, this._maxX - this._maxB);
			Coordinate coord3 = new Coordinate(this._maxX, this._maxA - this._maxX);
			Coordinate coord4 = new Coordinate(this._minA - this._minY, this._minY);
			Coordinate coord5 = new Coordinate(this._minY + this._maxB, this._minY);
			Coordinate coord6 = new Coordinate(this._maxY + this._minB, this._maxY);
			Coordinate coord7 = new Coordinate(this._maxA - this._maxY, this._maxY);
			IPrecisionModel precisionModel = geomFactory.PrecisionModel;
			precisionModel.MakePrecise(coordinate);
			precisionModel.MakePrecise(coord);
			precisionModel.MakePrecise(coord2);
			precisionModel.MakePrecise(coord3);
			precisionModel.MakePrecise(coord4);
			precisionModel.MakePrecise(coord5);
			precisionModel.MakePrecise(coord6);
			precisionModel.MakePrecise(coord7);
			CoordinateList coordinateList = new CoordinateList();
			coordinateList.Add(coordinate, false);
			coordinateList.Add(coord, false);
			coordinateList.Add(coord6, false);
			coordinateList.Add(coord7, false);
			coordinateList.Add(coord3, false);
			coordinateList.Add(coord2, false);
			coordinateList.Add(coord5, false);
			coordinateList.Add(coord4, false);
			if (coordinateList.Count == 1)
			{
				return geomFactory.CreatePoint(coordinate);
			}
			Coordinate[] coordinates;
			if (coordinateList.Count == 2)
			{
				coordinates = coordinateList.ToCoordinateArray();
				return geomFactory.CreateLineString(coordinates);
			}
			coordinateList.Add(coordinate, false);
			coordinates = coordinateList.ToCoordinateArray();
			return geomFactory.CreatePolygon(geomFactory.CreateLinearRing(coordinates), null);
		}

		private static readonly double SQRT2 = System.Math.Sqrt(2.0);

		private double _minX = double.NaN;

		private double _maxX;

		private double _minY;

		private double _maxY;

		private double _minA;

		private double _maxA;

		private double _minB;

		private double _maxB;

		private class BoundingOctagonComponentFilter : IGeometryComponentFilter
		{
			public BoundingOctagonComponentFilter(OctagonalEnvelope octagonalEnvelope)
			{
				this._octogonalEnvelope = octagonalEnvelope;
			}

			public void Filter(IGeometry geom)
			{
				if (geom is ILineString)
				{
					this._octogonalEnvelope.ExpandToInclude(((ILineString)geom).CoordinateSequence);
					return;
				}
				if (geom is IPoint)
				{
					this._octogonalEnvelope.ExpandToInclude(((IPoint)geom).CoordinateSequence);
				}
			}

			private readonly OctagonalEnvelope _octogonalEnvelope;
		}
	}
}
